#pragma clang diagnostic push
#pragma ide diagnostic ignored "hicpp-signed-bitwise"
//
// Created by hugan on 2020/10/16.
//

#include "JoystickManager.h"

static bool logDebug = false;
static bool logInfo = false;

JoystickManager::JoystickManager() {
    if (logDebug) std::cout << "JoystickManager.init" << std::endl;
}

JoystickManager::~JoystickManager() {
    //经idea调试:用户正常关闭程序,或idea[强制Stop]时,都会被调用
    if (logDebug) std::cout << "JoystickManager.delete" << std::endl;
    release();
}

void JoystickManager::release() {
    if (logDebug) std::cout << "JoystickManager.release" << std::endl;
    doFreeDevice();
    (g_deviceStateCallback) = nullptr;
    SAFE_RELEASE(g_pDirectInput)
}

HRESULT JoystickManager::createDirectInput8(bool isUtf8, func_deviceStateCallback deviceStateCallback) {
    HRESULT hr = DirectInput8Create(GetModuleHandle(nullptr), DIRECTINPUT_VERSION,
                                    IID_IDirectInput8, (void **) &g_pDirectInput, nullptr);
//                              IID_IDirectInput8, NULL, nullptr);//test
    if (FAILED(hr)) return hr;
    g_isUtf8 = isUtf8;
    g_deviceStateCallback = deviceStateCallback;
    JoystickUtil::initEmptyState(g_emptyState);
    return hr;
}

HRESULT JoystickManager::enumDevices(func_enumDevicesCallback callback) {
    if (!g_pDirectInput) return DIERR_NOTINITIALIZED;

    EnumDeviceParam enumDeviceParam = {};
    enumDeviceParam.isUtf8 = g_isUtf8;
    enumDeviceParam.callback = callback;
    /*
     * 参数1:
     * 改变后可以得到其他设备,鼠键等
     * 参数4:
     * 设备的连接状态,如:全部 DIEDFL_ALLDEVICES;已连接 DIEDFL_ATTACHEDONLY;
     * 实测后,没使用意义,没连接设备时[全部]也是不显示断开设备的,其他类型也不能显示断开设备
     */
    return g_pDirectInput->EnumDevices(
//            DI8DEVCLASS_ALL,
            DI8DEVCLASS_GAMECTRL,
            [](const DIDEVICEINSTANCE *pdidInstance, VOID *pContext) -> BOOL CALLBACK {
                auto *param = (EnumDeviceParam *) pContext;

                EnumDeviceData data;
                //设备名,pdidInstance总是GBK的
                if (param->isUtf8) {
                    //GBK转UTF8
                    std::string strAnsi = pdidInstance->tszInstanceName;
                    std::string strUtf8;
                    CharsetConverter::ansi2utf8(strAnsi, strUtf8);
                    data.tszInstanceName = strUtf8.c_str();
                } else {
                    //直接返回GBK的字符串
                    data.tszInstanceName = pdidInstance->tszInstanceName;
                }
                //guid
                char tempGuid[36];
                JoystickUtil::getGUIDString(pdidInstance->guidInstance, tempGuid);
                data.guid = tempGuid;

                param->callback(data);
                return DIENUM_CONTINUE;
            }, &enumDeviceParam, DIEDFL_ATTACHEDONLY);
}

vector<string> JoystickManager::getOnlineGuid() {
    vector<string> list;
    g_pDirectInput->EnumDevices(
            DI8DEVCLASS_GAMECTRL,
            [](const DIDEVICEINSTANCE *pdidInstance, VOID *pContext) -> BOOL CALLBACK {
                auto *list = (vector<string> *) pContext;
                char tempGuid[36];
                JoystickUtil::getGUIDString(pdidInstance->guidInstance, tempGuid);
                list->push_back(tempGuid);
//                list->emplace_back("dev" + to_string(list->size()));
                return DIENUM_CONTINUE;
            }, &list, DIEDFL_ATTACHEDONLY);
    return list;
}

void JoystickManager::startListenState(const char *guidString, int stateScanSpan, int stickRange, bool breakIfErr) {
    GUID deviceGuid;
    JoystickUtil::getGuid(deviceGuid, guidString);
    startListenState(deviceGuid, stateScanSpan, stickRange, breakIfErr);
}

void JoystickManager::startListenState(GUID guid, int stateScanSpan, int stickRange, bool breakIfErr) {
    if (!g_pDirectInput) {
        g_deviceStateCallback(g_emptyState, DIERR_NOTINITIALIZED);
        return;
    }

    doFreeDevice();
    HRESULT hr;
    //连接设备
    if (FAILED(hr = g_pDirectInput->CreateDevice(guid, &g_pJoystick, nullptr))) {
        if (logDebug) cout << "CreateDevice.err" << endl;
        g_deviceStateCallback(g_emptyState, hr);
        return;
    }
    //GetDeviceState前必须SetDataFormat
    if (FAILED(hr = g_pJoystick->SetDataFormat(&c_dfDIJoystick2))) {
        if (logDebug) cout << "SetDataFormat.err" << endl;
        g_deviceStateCallback(g_emptyState, hr);
        return;
    }
    //设置占用模式:非独占&后台(没有传入窗口)
    if (FAILED(g_pJoystick->SetCooperativeLevel(nullptr, DISCL_NONEXCLUSIVE | DISCL_BACKGROUND))) {
        if (logDebug) cout << "SetCooperativeLevel.err" << endl;
        g_deviceStateCallback(g_emptyState, hr);
        return;
    }
    //设置摇杆范围
    EnumObjectsParam enumObjectsParam = {};
    enumObjectsParam.stickRange = stickRange;
    enumObjectsParam.g_pJoystick = g_pJoystick;

    if (FAILED(g_pJoystick->EnumObjects(
            [](const DIDEVICEOBJECTINSTANCE *pdidoi, VOID *pContext) -> BOOL CALLBACK{
            auto * param = (EnumObjectsParam *) pContext;

            if (logDebug) cout << "rang=" << *&(param->stickRange) << endl;
            if (pdidoi->dwType & DIDFT_AXIS) {
                //设置摇杆值的范围,不设置的话,得到的是未经过处理的long,原点值不是0
                DIPROPRANGE diprg;
                diprg.diph.dwSize = sizeof(DIPROPRANGE);
                diprg.diph.dwHeaderSize = sizeof(DIPROPHEADER);
                diprg.diph.dwHow = DIPH_BYID;
                diprg.diph.dwObj = pdidoi->dwType;
                diprg.lMin = -param->stickRange;//上下界相等时,原点值为0
                diprg.lMax = +param->stickRange;
                if (logDebug) cout << "pdidoi->dwType=" << pdidoi->dwType << endl;
                if (FAILED(param->g_pJoystick->SetProperty(DIPROP_RANGE, &diprg.diph))) {
                    std::cerr << __FILE__ << "[row:" << __LINE__ << "]:setStickRange.err= " << GetLastError()
                              << std::endl;
                }
            }
            //设置成功或失败都是continue
            return DIENUM_CONTINUE;
    }, &enumObjectsParam, DIDFT_ALL))) {
        g_deviceStateCallback(g_emptyState, hr);
        return;
    }
    //阻塞当前线程,轮询监听
    DIDEVICEINSTANCE didi;
    didi.dwSize = sizeof(DIDEVICEINSTANCE);
    g_pJoystick->GetDeviceInfo(&didi);
    if (logInfo)
        cout << "startListen=" << didi.tszInstanceName << ", stateScanSpan=" << stateScanSpan
             << ", stickRange=" << stickRange << endl;

    g_listenState = true;
    g_pJoystick->Acquire();
    while (g_listenState) {
        hr = handleJoystickState();
        if (hr != S_OK && breakIfErr) break;
        Sleep(static_cast<DWORD>(stateScanSpan));
    }
    /*
     * 调用了stopListenState,跳出循环
     * .发送最后一个状态消息:DIERR_INPUTLOST
     * .释放对象
     */
    g_deviceStateCallback(g_emptyState, DIERR_INPUTLOST);
    doFreeDevice();
}

HRESULT JoystickManager::handleJoystickState() {
    //cout << "handleJoystickState.1" << endl;
    /*
    * 失败的结果:DIERR_INPUTLOST or DIERR_NOTACQUIRED
    * 情况:[前台模式]下,窗口失去焦点(本类是后台模式);设备断开
    * 措施:重新Acquire,如果失败,则直接返回,让外循环按 stateScanSpan继续处理
    */
    HRESULT hr;
    if (FAILED(hr = g_pJoystick->Poll())) {
        if (lastStateUpdated) {
            //只在首次出错,发送空状态
            lastStateUpdated = false;
            g_deviceStateCallback(g_emptyState, hr);
        }
        if (FAILED (hr = g_pJoystick->Acquire())) return hr;
    }
    //cout << "handleJoystickState.2" << endl;
    DIJOYSTATE2 state;
    if (FAILED(hr = g_pJoystick->GetDeviceState(sizeof(DIJOYSTATE2), &state))) {
        if (lastStateUpdated) {
            lastStateUpdated = false;
            g_deviceStateCallback(g_emptyState, hr);
        }
        return hr;
    }
    //[上次状态]不为空且状态相同,忽略事件
//    cout << "lastStateUpdated=" << lastStateUpdated << endl;
    if (lastStateUpdated) {
        bool same = JoystickUtil::equalDIJoyState2(&lastUpdatedState, &state);
//        cout << "same=" << same << endl;
        if (same) return S_OK;//过滤与上次一样状态的事件
    }

    if (!lastStateUpdated) {
        //首次连上,发送空状态
        if (logInfo)
            cout << "firstConnect" << endl;
        g_deviceStateCallback(g_emptyState, S_OK);
        //lastStateUpdated = state;不能赋值,否则会在下一次事件中 发送了关机前的状态,原因不明
        lastStateUpdated = true;
    } else {
        g_deviceStateCallback(state, S_OK);
        lastUpdatedState = state;
        lastStateUpdated = true;
    }
    //怎么释放内存??
    return S_OK;
}

void JoystickManager::stopListenState() {
    g_listenState = false;
}

void JoystickManager::doFreeDevice() {
    g_listenState = false;
    lastStateUpdated = false;
    if (g_pJoystick) g_pJoystick->Unacquire();
    SAFE_RELEASE(g_pJoystick)
}
